package cmd

import (
	"io"
	"io/ioutil"
	"path/filepath"
	"strings"

	ckeapi "cke.io/ckectl/app/apis"
	phases "cke.io/ckectl/app/cmd/phases/watch"
	kubeadmutil "cke.io/ckectl/app/util"
	"github.com/spf13/cobra"
	flag "github.com/spf13/pflag"
	kubeoptions "k8s.io/kubernetes/cmd/kubeadm/app/cmd/options"
	"k8s.io/kubernetes/cmd/kubeadm/app/cmd/phases/workflow"
)

// initOptions defines all the init options exposed via flags by kubeadm init.
// Please note that this structure includes the public kubeadm config API, but only a subset of the options
// supported by this api will be exposed as a flag.
type watchOptions struct {
	cfgPath        string
}

func NewCmdWatch(out io.Writer, watchOptions *watchOptions) *cobra.Command {
	if watchOptions == nil {
		watchOptions = newWatchOptions()
	}
	prepareRunner := workflow.NewRunner()

	cmd := &cobra.Command{
		Use:   "watch",
		Short: "Run this command in order to prepare files for the Kubernetes Deploy",
		Run: func(cmd *cobra.Command, args []string) {
			err := prepareRunner.Run(args)
			kubeadmutil.CheckErr(err)
		},
		Args: cobra.NoArgs,
	}

	AddWatchConfigFlags(cmd.Flags(), watchOptions)
	prepareRunner.AppendPhase(phases.NewWatchPhase())
	SetPhasesFlags(prepareRunner.Phases, GetFlagSetNames(cmd.Flags())...)

	// sets the data builder function, that will be used by the runner
	// both when running the entire workflow or single phases
	prepareRunner.SetDataInitializer(func(cmd *cobra.Command, args []string) (workflow.RunData, error) {
		return newWatchData(cmd, args, watchOptions, out)
	})

	// binds the Runner to kubeadm init command by altering
	// command help, adding --skip-phases flag and by adding phases subcommands
	prepareRunner.BindToCommand(cmd)

	return cmd
}

// AddClusterConfigFlags adds cluster flags bound to the config to the specified flagset
func AddWatchConfigFlags(flagSet *flag.FlagSet, opt *watchOptions) {
	flagSet.StringVar(
		&opt.cfgPath, kubeoptions.CfgPath, opt.cfgPath,
		"Use alternative range of IP address for service VIPs.",
	)
}

// newInitOptions returns a struct ready for being used for creating cmd init flags.
func newWatchOptions() *watchOptions {
	return &watchOptions{}
}

// newInitData returns a new initData struct to be used for the execution of the kubeadm init workflow.
// This func takes care of validating initOptions passed to the command, and then it converts
// options into the internal InitConfiguration type that is used as input all the phases in the kubeadm init workflow
func newWatchData(cmd *cobra.Command, args []string, options *watchOptions, out io.Writer) (*ckeapi.WatchData, error) {
	return &ckeapi.WatchData{
		Nodes:    GetGroup("[kube-node]", options.cfgPath),
		PhaseNum: getPhaseLength( GetUpPhase()),
	}, nil
}

func getPhaseLength(phases []workflow.Phase) int {
	if len(phases) == 0 {
		return 1
	}

	var num int
	for _, phase := range phases {
		num += getPhaseLength(phase.Phases)
	}
	return num
}

func GetGroup(group string, path string) []ckeapi.K8sNode {
	b, err := ioutil.ReadFile(filepath.Clean(path))
	if err!=nil {
		return []ckeapi.K8sNode{}
	}
	defer func() { _ = recover() }()

	var nodeList []ckeapi.K8sNode
	nodeMap := getMasterNode( strings.ReplaceAll(string(b), "\r", ""))

	names := strings.Split( strings.Split( strings.Split(string(b), group)[1], "[")[0], "\n")

	for _, v := range filterSpace(names) {
		nodeList = append(nodeList, ckeapi.K8sNode{Name: v, Ip: nodeMap[v].Ip})
	}

	return nodeList
}

func getMasterNode(cfg string) (nodeMap map[string]ckeapi.K8sNode){
	nodeMap = make( map[string]ckeapi.K8sNode, 0)
	infos := strings.Split( strings.Split( strings.Split(cfg, "[all]")[1], "[")[0], "\n")

	for _, v := range filterSpace(infos) {

		name     := strings.Split(v, " ")[0]
		ip       := strings.Split(strings.Split(v, "ip=")[1], " ")[0]

		nodeMap[name] = ckeapi.K8sNode{Name: name, Ip: ip}
	}
	return
}

func filterSpace(oldSls []string) (newSls []string) {
	for _, word := range oldSls {
		if word != "" {
			newSls = append(newSls, word)
		}
	}
	return newSls
}